﻿HmJS.$Import('core.element');

/*
---
scirpt: HoverGroup
version: 1.0-0
authors: Aaron Newton
description: Manages mousing in and out of multiple objects (think drop-down menus).
license: MIT-Style License
download: http://mootools.net/forge/p/hovergroup
source: http://github.com/anutron/clientcide-hovergroup

requires:
core/1.2.4:
- Class.Extras
- Element.Event
more/1.2.4.2:
- Class.Binds

provides:
- HoverGroup
...
*/

/*
How to use
----------

### Requires

* [MooTools Core 1.2.3](http://mootools.net/core): Class.Extras, Element.Event (and their dependencies)
* [MooTools More 1.2.3.1](http://mootools.net/more): Class.Binds (and its dependences)

### Syntax

	new HoverGroup(options);

### Arguments

1. options - (*object*) an object of key/value settings

### Options

* onEnter - (*function*) callback fired when the mouse enters the group
* onLeave - (*function*) callback fired when the mouse leaves the group
* elements - (*mixed*) collection of elements, a selector, an id, or an element to monitor for mouse enter/leave behavior
* delay - (*integer*) time (in ms) alloted for the mouse to leave one item in the group before it must enter another item in order to fire either the *onEnter* or *onLeave* events. Defaults to 300.
* start - (*array*) an array of events that can cause the *onEnter* event to fire. Defaults to *['mouseenter']* but could easily be, for example, *['click']*.
* remain - (*array*) the events that keep the group open. Events that will keep the group from firing the *onLeave* event but only after the *onEnter* event has fired. If the *start* array doesn't include *mouseenter*, for example, you might want to set remains to *['mouseenter']*. This would allow the user to click to open the group, but mouse around the group to keep it open. If *start* contains *mouseenter*, there's no need to specify it in the *remain* group. This value starts off as an empty array, as the default for *start* is *['mouseenter']*.
* end - (*array*) the events that cause the *onLeave* event to fire. Defaults to *['mouseleave']*.

HoverGroup method: attachTo {#HoverGroup:attachTo}
--------------------------------------------------

Attaches and detaches elements to the group. By default, the elements specified in the options are automatically attached, but you and add or remove items from the group at will.

### Syntax

	myHoverGroup.attachTo(elements[, detach]);

### Arguments

1. elements - (*mixed*) collection of elements, a selector, an id, or an element to attach to the group.
2. detach - (*boolean*) if *true* the elements will be removed from the group.

### Returns

* (*object*) - This instance of [HoverGroup][]

[HoverGroup]: #HoverGroup
[Options]: http://www.mootools.net/docs/core/Class/Class.Extras#Options
[Events]: http://www.mootools.net/docs/core/Class/Class.Extras#Events
*/

HmJS.register('util.dom.HoverGroup', function ($ns) {

	return new Class({

		Implements: [Options, Events],

		Binds: ['enter', 'leave', 'remain'],

		options: {
			//onEnter: $empty,
			//onLeave: $empty,
			elements: [],
			delay: 300,
			start: ['mouseenter'],
			remain: [],
			end: ['mouseleave']
		},

		initialize: function (options) {
			this.setOptions(options);
			this.attachTo(this.options.elements);
			this.addEvents({
				leave: function () {
					this.active = false;
				},
				enter: function () {
					this.active = true;
				}
			});
		},

		elements: [],

		attachTo: function (elements, detach) {
			var starters = {},
					remainers = {},
					enders = {};
			elements = Array.from(document.id(elements) || $$(elements));
			this.options.start.each(function (start) {
				starters[start] = this.enter;
			}, this);
			this.options.end.each(function (end) {
				enders[end] = this.leave;
			}, this);
			this.options.remain.each(function (remain) {
				remainers[remain] = this.remain;
			}, this);
			if (detach) {
				elements.each(function (el) {
					el.removeEvents(starters).removeEvents(enders).removeEvents(remainers);
					this.elements.erase(el);
				}, this);
			} else {
				elements.each(function (el) {
					el.addEvents(starters).addEvents(enders).addEvents(remainers);
				});
				this.elements.combine(elements);
			}
			return this;
		},

		detachFrom: function (elements) {
			this.attachTo(elements, true);
		},

		enter: function (e) {
			this.isMoused = true;
			this.assert(e);
		},

		leave: function (e) {
			this.isMoused = false;
			this.assert(e);
		},

		remain: function (e) {
			if (this.active) { this.enter(e); }
		},

		assert: function (e) {
			clearTimeout(this.assertion);
			this.assertion = (function () {
				if (!this.isMoused && this.active) {
					this.fireEvent('leave', e);
				} else if (this.isMoused && !this.active) {
					this.fireEvent('enter', e);
				}
			}).delay(this.options.delay, this);
		}

	});

});